﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TheSnakeGame.Engine.GameObjects;
using TheSnakeGame.Engine;

namespace Tests.TheSnakeGame.Engine
{
    [TestClass]
    public class SnakeHeadTests
    {
        #region Create Snake
        [TestMethod]
        public void CreateNewSnake_WithLengthZero_ShouldGetOnlyHead()
        {
            var snake = new SnakeHead(new Point(0, 0), 1);

            Assert.AreEqual(0, snake.Position.X, "Incorrect X position");
            Assert.AreEqual(0, snake.Position.Y, "Incorrent Y position");
            Assert.AreEqual(1, snake.Length, "Snake length should be 1");
            Assert.AreEqual(0, snake.Body.Count(), "Snake body parts count must be 0");
        }

        [TestMethod]
        public void CreateNewSnake_EnsureBodyPartsAreGeneratedAtCorrectPositions()
        {
            var x = 5;
            var y = 6;
            var length = 7;
            var snake = new SnakeHead(new Point(x, y), length);

            Assert.AreEqual(x, snake.Position.X, "Incorrect X position");
            Assert.AreEqual(y, snake.Position.Y, "Incorrent Y position");
            Assert.AreEqual(length, snake.Length, "Snake length should be " + length);
            Assert.AreEqual(length - 1, snake.Body.Count(), "Snake body parts count must be " + (length - 1));

            var i = 0;
            foreach (var bodyPart in snake.Body)
            {
                Assert.AreEqual(x + i + 1, bodyPart.Position.X, "Incorrectly place body part");
                Assert.AreEqual(y, bodyPart.Position.Y, "Incorrectly place body part");
                i++;
            }
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateSnakeWithZeroLength_SouldThrowException()
        {
            var snake = new SnakeHead(new Point(1, 1), 0);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateSnake_WithNegativePositionX_ShouldTHrowException()
        {
            var snake = new SnakeHead(new Point(-1, 5), 5);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateSnake_WithNegativePositionY_ShouldTHrowException()
        {
            var snake = new SnakeHead(new Point(5, -1), 5);
        }

        [TestMethod]
        public void CreateNewSnake_InitialDirrectionShouldBeLeft()
        {
            var snake = new SnakeHead(new Point(10, 10), 10);
            Assert.AreEqual(Directions.Left, snake.MoveDirection, "Initial dirrection should be left");
        }
        #endregion

        #region Snake DoAction

        [TestMethod]
        public void DoAction_SnakeHeadOnly_DirectionLeft_SnakeShouldMoveInCorrectDirrection()
        {
            var snake = new SnakeHead(new Point(10, 10), 1);
            snake.MoveDirection = Directions.Left;
            snake.DoAction();
            Assert.AreEqual(Directions.Left, snake.MoveDirection, "Snake direction changed unexpectedly");
            Assert.AreEqual(9, snake.Position.X, "When moving left the snake position did not changed with -1 in X direction");
            Assert.AreEqual(10, snake.Position.Y, "When moving left the snake posistion should not change in Y direction");
        }

        [TestMethod]
        public void DoAction_SnakeHeadOnly_DirectionTop_SnakeShouldMoveInCorrectDirrection()
        {
            var snake = new SnakeHead(new Point(10, 10), 1);
            snake.MoveDirection = Directions.Top;
            snake.DoAction();
            Assert.AreEqual(Directions.Top, snake.MoveDirection, "Snake direction changed unexpectedly");
            Assert.AreEqual(10, snake.Position.X, "When moving top the snake position should not change in X direction");
            Assert.AreEqual(11, snake.Position.Y, "When moving top the snake position did not changed with +1 in Y direction");
        }

        [TestMethod]
        public void DoAction_DirectionRight_SnakeShouldMoveInCorrectDirrection()
        {
            var snake = new SnakeHead(new Point(10, 10), 1);
            snake.MoveDirection = Directions.Top; // Otherwise direction cannot be changed to right because of original left snake direction
            snake.MoveDirection = Directions.Right;
            snake.DoAction();
            Assert.AreEqual(Directions.Right, snake.MoveDirection, "Snake direction changed unexpectedly");
            Assert.AreEqual(11, snake.Position.X, "When moving right the snake position did not changed with +1 in X direction");
            Assert.AreEqual(10, snake.Position.Y, "When moving top the snake position should not change in X direction");

        }

        [TestMethod]
        public void DoAction_DirectionButtom_SnakeShouldMoveInCorrectDirrection()
        {
            var snake = new SnakeHead(new Point(10, 10), 1);
            snake.MoveDirection = Directions.Bottom;
            snake.DoAction();
            Assert.AreEqual(Directions.Bottom, snake.MoveDirection, "Snake direction changed unexpectedly");
            Assert.AreEqual(10, snake.Position.X, "When moving buttom the snake position should not change in X direction");
            Assert.AreEqual(9, snake.Position.Y, "When moving buttom the snake position did not changed with -1 in Y direction");
 
        }

        [TestMethod]
        public void DoAction_MoveLeft_EnsureTheSnakeBodyFollows()
        {
            var snake = new SnakeHead(new Point(10, 10), 3);
            snake.MoveDirection = Directions.Left;
            snake.DoAction();

            Assert.AreEqual(9, snake.Position.X, "Snake head moved incorrectly");
            Assert.AreEqual(10, snake.Position.Y, "Snake head moved incorrectly");

            Assert.AreEqual(10, snake.Body.First().Position.X, "Last body part does not have the original head coordinates");
            Assert.AreEqual(10, snake.Body.First().Position.Y, "Last body part does not have the original head coordinates");

            Assert.AreEqual(11, snake.Body.Last().Position.X, "Body part did not follow correctly");
            Assert.AreEqual(10, snake.Body.Last().Position.Y, "Body part did not follow correctly");

        }

        [TestMethod]
        public void DoAction_MoveRight_EnsureTheSnakeBodyFollows()
        {
            var snake = new SnakeHead(new Point(10, 10), 3);
            snake.MoveDirection = Directions.Top; // Direction cannot be changed directly to opposite direction
            snake.MoveDirection = Directions.Right;
            snake.Position.X = 13;
            snake.Body.First().Position.X = 12;
            snake.Body.Last().Position.X = 11;
            snake.DoAction();

            Assert.AreEqual(14, snake.Position.X, "Snake head moved incorrectly");
            Assert.AreEqual(10, snake.Position.Y, "Snake head moved incorrectly");

            Assert.AreEqual(13, snake.Body.First().Position.X, "Last body part does not have the original head coordinates");
            Assert.AreEqual(10, snake.Body.First().Position.Y, "Last body part does not have the original head coordinates");

            Assert.AreEqual(12, snake.Body.Last().Position.X, "Body part did not follow correctly");
            Assert.AreEqual(10, snake.Body.Last().Position.Y, "Body part did not follow correctly");

        }

        [TestMethod]
        public void DoAction_MoveTop_EnsureTheSnakeBodyFollows()
        {
            var snake = new SnakeHead(new Point(10, 10), 3);
            snake.Body.First().Position.X -= 1;
            snake.Body.First().Position.Y -= 1;
            snake.Body.Skip(1).First().Position.X -= 2;
            snake.Body.Skip(1).First().Position.Y -= 2;
            snake.MoveDirection = Directions.Top;
            snake.DoAction();

            Assert.AreEqual(10, snake.Position.X, "Snake head moved incorrectly");
            Assert.AreEqual(11, snake.Position.Y, "Snake head moved incorrectly");

            Assert.AreEqual(10, snake.Body.First().Position.X, "Last body part does not have the original head coordinates");
            Assert.AreEqual(10, snake.Body.First().Position.Y, "Last body part does not have the original head coordinates");

            Assert.AreEqual(10, snake.Body.Last().Position.X, "Body part did not follow correctly");
            Assert.AreEqual(9, snake.Body.Last().Position.Y, "Body part did not follow correctly");
        }

        [TestMethod]
        public void DoAction_MoveButtom_EnsureTheSnakeBodyFollows()
        {
            var snake = new SnakeHead(new Point(10, 10), 3);
            snake.Position.Y = 8;
            snake.Body.First().Position.X -= 1;
            snake.Body.First().Position.Y -= 1;
            snake.Body.Skip(1).First().Position.X -= 2;
            snake.Body.Skip(1).First().Position.Y -= 0;
            snake.MoveDirection = Directions.Bottom;
            snake.DoAction();

            Assert.AreEqual(10, snake.Position.X, "Snake head moved incorrectly");
            Assert.AreEqual(7, snake.Position.Y, "Snake head moved incorrectly");

            Assert.AreEqual(10, snake.Body.First().Position.X, "Last body part does not have the original head coordinates");
            Assert.AreEqual(8, snake.Body.First().Position.Y, "Last body part does not have the original head coordinates");

            Assert.AreEqual(10, snake.Body.Last().Position.X, "Body part did not follow correctly");
            Assert.AreEqual(9, snake.Body.Last().Position.Y, "Body part did not follow correctly");
        }
        #endregion

        #region Change Direction
        [TestMethod]
        public void DoAction_MoveLeft_ShouldNotBeAbleToTurnRight()
        {
            var snake = new SnakeHead(new Point(10, 10), 10);
            snake.MoveDirection = Directions.Left;
            snake.MoveDirection = Directions.Right;
            Assert.AreEqual(Directions.Left, snake.MoveDirection, "If snake is moving left, it should not be able to turn right");
        }

        [TestMethod]
        public void DoAction_MoveRight_ShouldNotBeAbleToTurnLeft()
        {
            var snake = new SnakeHead(new Point(10, 10), 10);
            snake.MoveDirection = Directions.Top;
            snake.MoveDirection = Directions.Right;
            snake.MoveDirection = Directions.Left;
            Assert.AreEqual(Directions.Right, snake.MoveDirection, "If snake is moving right, it should not be able to turn left");
        }

        [TestMethod]
        public void DoAction_MoveButtom_ShouldNotBeABleToTurnTop()
        {
            var snake = new SnakeHead(new Point(10, 10), 10);
            snake.MoveDirection = Directions.Bottom;
            snake.MoveDirection = Directions.Top;
            Assert.AreEqual(Directions.Bottom, snake.MoveDirection, "If snake is moving buttom, it should not be able to turn top");
        }

        [TestMethod]
        public void DoAction_MoveTop_ShouldNotBeAbleToTurnTop()
        {
            var snake = new SnakeHead(new Point(10, 10), 10);
            snake.MoveDirection = Directions.Top;
            snake.MoveDirection = Directions.Bottom;
            Assert.AreEqual(Directions.Top, snake.MoveDirection, "If snake is moving top, it should not be able to turn buttom");
        }
        #endregion
    }
}
